/**
 * @file NetlistManager.hh
 * @author Xiaoze Lin (linxiaoze96@gmail.com)
 * @brief The netlsit manager class.
 * @version 0.1
 *
 * @copyright Copyright (c) 2024
 *
 */
#pragma once

#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/strong_components.hpp>
#include <fstream>
#include <iostream>
#include <set>
#include <string>
#include <vector>

#include "Netlist.hh"
#include "fsim/AtpgLibrary.hh"
#include "log/Log.hh"
#include "verilog/VerilogReader.hh"

struct node_properties
{
  int component;
};
struct edge_properties
{
};
struct graph_properties
{
  int number_of_components;
};

typedef boost::adjacency_list<boost::vecS, boost::vecS, boost::directedS, node_properties, edge_properties, graph_properties> d_graph_t;
typedef typename boost::graph_traits<d_graph_t>::vertex_descriptor vertex_t;

namespace ifsim {

/**
 * @brief The netlist manager class
 */
class NetlistManager
{
 public:
  static NetlistManager* getOrCreateNetlistManager();
  static void destroyNetlistManager();

  // verilog -> std cell netlist
  bool readVerilog(const char* verilog_file);
  void verilogToStdCellNetlist(std::string top_module_name, AtpgLibrary* atpg_lib);

  // std cell netlist -> primitive netlist
  void stdCellNetlistToPrimitiveNetlist(AtpgLibrary* atpg_lib);
  bool checkTriState(const vector<int>& multi_pid);
  bool checkPredInout(const StdCellandIONode& snode, int port, std::string& inout_name, int& inout_pi_id);
  void gateEval(const PrimitiveGate pnode, std::map<int, int>& gmvs, int& val);
  void simPrimitiveNetlistBasedOnTieNodes(std::set<int>& found_tie0_nodes, std::set<int>& found_tie1_nodes);
  void writePrimitiveNetlistToFile(std::string file_name);

  // primitive netlist -> sim netlist
  void genLevelizedNetlist(bool print_debug_info);
  void identifyFeedbackRegions(std::vector<PrimitiveGate>& copied_nodes, std::vector<std::vector<int>>& sccs);
  void identifyCombinationalLoops(const std::vector<std::vector<int>>& sccs, std::vector<std::vector<int>>& loops,
                                  std::vector<std::vector<int>>& fbbufs_per_scc, std::vector<std::vector<int>>& latches_per_scc);
  bool succDepthFirstSearch(std::vector<int>& loop, std::set<int>& visit, const PrimitiveGate curr_pnode, const int source_id);
  void addTIEXInConstantValueLoop(const std::vector<std::vector<int>>& loops, const std::vector<std::vector<int>>& fbbufs_per_scc,
                                  std::set<int>& add_tiex_loops);
  void calculateDepthToPO(std::vector<int>& depth_to_po);
  void addFeedbackBufInLoop(const std::vector<std::vector<int>>& loops, const std::vector<std::vector<int>>& fbbufs_per_scc,
                            const std::vector<int> depth_to_po, const std::set<int>& add_tiex_loops);
  void identifyPotentialOscillateLoops(const std::vector<std::vector<int>>& loops, const std::set<int>& add_tiex_loops);
  void levelizeOptimizedNodes(const std::vector<PrimitiveGate>& pre_level_optnodes, std::vector<PrimitiveGate>& post_level_optnodes,
                              std::vector<int>& levels, std::vector<int>& levels_post_opt);
  void computeLevelOfOptimizedNodes(const std::vector<PrimitiveGate>& pre_level_optnodes, std::vector<int>& level);
  void compileNodeInfoForLevelization(const std::vector<PrimitiveGate>& pre_level_optnodes, std::vector<int>& input_nodes,
                                      std::vector<int>& tie_nodes, std::vector<int>& output_nodes, std::vector<int>& de_nodes,
                                      std::vector<int>& dff_nodes, std::vector<int>& dla_nodes, std::vector<int>& fbbuf_nodes);
  int generatePostLevelOptNodesWithTypeGrouping(const std::vector<PrimitiveGate>& pre_level_optnodes, const std::vector<int>& levels,
                                                std::vector<PrimitiveGate>& post_level_optnodes, std::vector<int>& levels_post_opt);
  int populateOptNodesForSimulation(const std::vector<PrimitiveGate>& post_level_optnodes, const std::vector<int>& levels_post_opt);

  const std::string& inputCktFile() const { return _inputCktFile; }
  void inputCktFile(const std::string& file) { _inputCktFile = file; }
  bool splitTieNodes() const { return _splitTieNodes; }
  void splitTieNodes(bool flag) { _splitTieNodes = flag; }
  int maxFanout() const { return _maxFanout; }
  void maxFanout(int num) { _maxFanout = num; }
  bool dffBuffers() const { return _dffBuffer; }
  void dffBuffers(bool flag) { _dffBuffer = flag; }

  StdCellNetlist& stdCellNetlist() { return _stdCellNetlist; }
  PrimitiveNetlist& primitiveNetlist() { return _priNetlist; }
  LevelizedNetlist& simNetlist() { return _simNetlist; }

 private:
  NetlistManager();
  ~NetlistManager();

  // verilog relevant
  std::string _inputCktFile;
  VerilogReader _verilogReader;
  std::vector<std::unique_ptr<VerilogModule>> _verilogModules;  // The current design parsed from verilog file.
  VerilogModule* _topModule = nullptr;                          // The design top module.

  // atpg library relevant
  AtpgLibrary* _atpgLib;

  // netlist relevant
  StdCellNetlist _stdCellNetlist;
  PrimitiveNetlist _priNetlist;
  LevelizedNetlist _simNetlist;

  // flags of processing pri netlist
  bool _splitTieNodes;  // whether to split tie nodes, default yes
  int _maxFanout;       // bigger than, then reduce, default 4
  bool _dffBuffer;      // whether to insert buffers between two DFFs, default yes

  // Singleton netlist manager.
  static NetlistManager* _netlistManager;
};
}  // namespace ifsim